#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <malloc.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

#define SERV_PORT 43211
#define MAXLINE 4096
#define LISTENQ 1024
#define BUFFER_SIZE 4096

#define MAXEVENTS 128

static void errExit(char *msg) {
  perror(msg);
  exit(EXIT_FAILURE);
}

static void make_nonblocking(int fd) { fcntl(fd, F_SETFL, O_NONBLOCK); }

static int tcp_nonblocking_server_listen(int port) {
  int listenfd;
  listenfd = socket(AF_INET, SOCK_STREAM, 0);

  make_nonblocking(listenfd);

  struct sockaddr_in server_addr;
  bzero(&server_addr, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_port = htons(port);

  int on = 1;
  setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

  int rt1 =
      bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
  if (rt1 < 0) {
    errExit("bind failed ");
  }

  int rt2 = listen(listenfd, LISTENQ);
  if (rt2 < 0) {
    errExit("listen failed ");
  }

  signal(SIGPIPE, SIG_IGN);

  return listenfd;
}

int main(int argc, char **argv) {
  int listen_fd, socket_fd;
  int n, i;
  int efd;
  struct epoll_event event;
  struct epoll_event *events;

  listen_fd = tcp_nonblocking_server_listen(SERV_PORT);

  efd = epoll_create1(0);
  if (efd == -1) {
    errExit("epoll create failed");
  }

  event.data.fd = listen_fd;
  event.events = EPOLLIN | EPOLLET;
  if (epoll_ctl(efd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
    errExit("epoll_ctl add listen fd failed");
  }

  /* Buffer where events are returned */
  events = calloc(MAXEVENTS, sizeof(event));

  while (1) {
    n = epoll_wait(efd, events, MAXEVENTS, -1);
    printf("epoll_wait wakeup\n");
    for (i = 0; i < n; i++) {
      if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) ||
          (!(events[i].events & EPOLLIN))) {
        fprintf(stderr, "epoll error\n");
        close(events[i].data.fd);
        continue;
      } else if (listen_fd == events[i].data.fd) {
        struct sockaddr_storage ss;
        socklen_t slen = sizeof(ss);
        int fd = accept(listen_fd, (struct sockaddr *)&ss, &slen);
        if (fd < 0) {
          errExit("accept failed");
        } else {
          make_nonblocking(fd);
          event.data.fd = fd;
          event.events = EPOLLIN | EPOLLET;  // edge-triggered
          //   event.events = EPOLLIN;  // level-triggered
          if (epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event) == -1) {
            errExit("epoll_ctl add connection fd failed");
          }
        }
        continue;
      } else {
        socket_fd = events[i].data.fd;
        printf("get event on socket fd == %d \n", socket_fd);
      }
    }
  }

  free(events);
  close(listen_fd);
}
